/**
 * @author Oz Madar
 * @mail - ozdevmail@gmail.com
 */

package ios;

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;

import javapns.Push;
import javapns.communication.exceptions.KeystoreException;
import javapns.devices.exceptions.InvalidDeviceTokenFormatException;
import javapns.notification.Payload;
import javapns.notification.PayloadPerDevice;
import javapns.notification.transmission.PushQueue;


public class RefeshablePushQueue{//TODO implements PushQueue{

	public static long DEFAULT_REFRESH_INTERVAL_PER_Q = 15 * 60 * 1000;//30min * 60sec * 1000ms == 30min in ms
	
	/** at each interval passed one queue will reconnect */
	private long refreshInervalMS;
	private int numberOfThreads;
	
	private final Object keystore;
	private final String password;
	private final boolean isProduction;

	private PushQueue[] queue;
	private Object[] queueLock;
	
	private LinkedBlockingDeque<PayloadPerDevice> payloads = new LinkedBlockingDeque<PayloadPerDevice>();

	private ExecutorService workersPool;
	
	
	/**
	 * * note that this queue drops the sent notification and does not keep track with anything (sent exceptions etc.) 
	 * @param numberOfThreads - 30 is good I think
	 * @throws KeystoreException
	 */
	public RefeshablePushQueue(Object keystore, String password, boolean production, int numberOfThreads) throws KeystoreException {
		this.numberOfThreads = numberOfThreads;
		this.keystore = keystore;
		this.password = password;
		this.isProduction = production;
		
		refreshInervalMS = DEFAULT_REFRESH_INTERVAL_PER_Q / numberOfThreads;
		workersPool = Executors.newCachedThreadPool();
		
		createQueues();
		startRefreshener();
		
	}
	
	private void startRefreshener() {
		new Thread("Refreshener"){
			public void run() {
				int i = 0;
				while(true){
					try {
						sleep(refreshInervalMS);
					} catch (InterruptedException e) {				
						e.printStackTrace();
						continue;
					}
					
					synchronized (queueLock[i]) {
						try {							
							queue[i] = Push.queue(keystore, password, isProduction, 1);
							queue[i].start();
							System.out.println(new Date(System.currentTimeMillis()) + " ----- notification  refreshing pushQ id="+i);
						} catch (KeystoreException e) {
							System.err.println("notification I don't expect you to be here");
							e.printStackTrace();
						}
					}
					i = (i+1) % numberOfThreads;
				}
				
			};
		}.start();		
	}

	private void createQueues() throws KeystoreException {
		queue = new PushQueue[numberOfThreads];
		queueLock = new Object[numberOfThreads];
		for (int i = 0; i < numberOfThreads; i++) {
			queue[i] = Push.queue(keystore, password, isProduction, 1);
			queue[i].start();
			queueLock[i] = new Object();
			workersPool.submit(createSender(i));
		}			
	}

	private Runnable createSender(int id) {
		return new Runnable() {
			int id;
			int numOfSent;
			@Override
			public void run() {
				PayloadPerDevice payload;
				numOfSent = 0;
				while(true){
					try {
						payload = payloads.takeLast();
						synchronized (queueLock[id]) {
							queue[id].add(payload);
							System.out.println("----------------notification sending from qid="+id);
							numOfSent = (numOfSent + 1);
							if (numOfSent % 100 == 0){
								queue[id].getCriticalExceptions();
								queue[id].clearPushedNotifications();								
							}
						}					
					} catch (InterruptedException e) {	e.printStackTrace();/* TODO */	}
					
				}				
			}
			public Runnable setId(int id) {
				this.id = id;
				return this;
			}
		}.setId(id);
	}

	/** time interval for each queue to reconnect */
	public void setRefreshInervalMS(long timeInervalMS) {
		this.refreshInervalMS = timeInervalMS / numberOfThreads;
	}
	
	public RefeshablePushQueue add(Payload payload, String token)	throws InvalidDeviceTokenFormatException {
	 	payloads.addLast(new PayloadPerDevice(payload, token));
	 	return this;
	}

	
}
